package de.syfy.project.game;

import de.syfy.project.engine.Sprite;
import de.syfy.project.engine.Tile;
import de.syfy.project.engine.interfaces.World;
import static org.lwjgl.opengl.GL11.*;
import de.syfy.project.utils.Tupel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.Point2D;

import java.util.List;
import java.util.Random;
import java.util.Vector;

/**
 *
 * @author Timo
 */
public class GameWorld implements World{
    private GamePanel panel;
    protected int max_X_Grid;
    protected int max_Y_Grid; 
    private Tile[][] tileArray;
    private boolean initialized = false;
    private Dimension size;
    private Point2D.Float actualPoint; //Oben Links
    private float dx_draw = 0;
    private float dy_draw = 0;
    private Vector<Sprite> actors;
    
    private GameWorld(int x_grid, int y_grid){
        max_X_Grid = x_grid;
        max_Y_Grid = y_grid;  
        this.size = new Dimension(x_grid*Tile.TILE_WIDTH,y_grid*Tile.TILE_HEIGHT);
        this.initialize();
    }
    
    public GameWorld(GamePanel panel, int x_grid, int y_grid){
        this(x_grid,y_grid);
        this.panel = panel; 
        actualPoint = new Point2D.Float(0,0);
    }
    
    public GameWorld(GamePanel panel, int x_grid, int y_grid, Point2D.Float actualPoint){
        this(x_grid,y_grid);
        this.panel = panel;
        this.actualPoint = actualPoint; 
    }   
    
    public GameWorld(GamePanel panel, Dimension size){
        this.panel = panel;
        this.size = size;
        max_X_Grid = size.width / Tile.TILE_WIDTH;
        max_Y_Grid = size.height / Tile.TILE_HEIGHT;
        actualPoint = new Point2D.Float(0,0);
        this.initialize();    
    }
    
    public GameWorld(GamePanel panel, Dimension size, Point2D.Float actualPoint){
        this.panel = panel;
        this.size = size;
        this.actualPoint = actualPoint;
        max_X_Grid = size.width / Tile.TILE_WIDTH;
        max_Y_Grid = size.height / Tile.TILE_HEIGHT;
        this.initialize(); 
    }

    @Override
    public void setActualCoordinate(Point2D.Float p) {
        float x = p.x;
        float y = p.y;
        dx_draw = -(x % (Tile.TILE_WIDTH));
        dy_draw = -(y % (Tile.TILE_HEIGHT));
        //if(x > actualPoint.x)dx_draw = -dx_draw;
        //if(y > actualPoint.y)dy_draw = -dy_draw;
        this.actualPoint = p;
    }

    @Override
    public Point2D.Float getActualCoordinate() {
        return actualPoint;
    }
    
    /*
     * Sollte alle Tiles die genutzt werden enthalten!
     * 
     * Enthält zur Zeit zum Testen roten Hintergrund!
     */
    @Override
    public final void initialize() {
        actors = new Vector<Sprite>();
        /*
         * Initialisierung des Tile-Arrays
         */
        Random random = new Random();
        Tile[] t = {new Tile(Color.red), 
                    new Tile(Color.green), 
                    new Tile(Color.blue)
                    };
        tileArray = new Tile[max_X_Grid][max_Y_Grid];
        for (int i = 0; i < max_X_Grid; i++) {
            for (int j = 0; j < max_Y_Grid; j++) {
                tileArray[i][j] = t[random.nextInt(t.length)];
            }
        }
        initialized = true;
    }

    @Override
    public Dimension getSize() {
        return size;
    }

    @Override
    public void addSprite(Sprite s) {
        actors.add(s);
    }

    @Override
    public List<Sprite> getSpritesCopy() {
        return (Vector<Sprite>)actors.clone();
    }

    @Override
    public Sprite getNearestSprite(Sprite s) {
        Sprite tmp = null;
        int old_distance = 0;
        int distance = 0;
        float dx = 0;
        float dy = 0;
        for(Sprite sprite: actors){
            dx = sprite.getPosition().x - s.getPosition().x;
            dy = sprite.getPosition().y - s.getPosition().y;
            distance = (int) Math.sqrt(dx*dx+dy*dy+0.5);
            if(distance < old_distance && sprite != s){
                tmp = sprite;
                old_distance = distance;
            }
        }
        return tmp;
    }

    @Override
    public List<Sprite> getSpritesInRange(Sprite s, float r) {
        List<Sprite> sprites = new Vector<Sprite>();
        int distance = 0;
        float dx = 0;
        float dy = 0;
        for(Sprite sprite: actors){
            dx = sprite.getPosition().x - s.getPosition().x;
            dy = sprite.getPosition().y - s.getPosition().y;
            distance = (int) Math.sqrt(dx*dx+dy*dy+0.5);
            if(distance <= r && sprite != s){
                sprites.add(sprite);
            }
        }
        if(sprites.isEmpty())return null;
        return sprites;
    }

    @Override
    public List<Sprite> getSpritesInRange(Point2D.Float p, float r) {
        List<Sprite> sprites = new Vector<Sprite>();
        int distance = 0;
        float dx = 0;
        float dy = 0;
        for(Sprite sprite: actors){
            dx = sprite.getPosition().x - p.x;
            dy = sprite.getPosition().y - p.y;
            distance = (int) Math.sqrt(dx*dx+dy*dy+0.5);
            if(distance <= r){
                sprites.add(sprite);
            }
        }
        if(sprites.isEmpty())return null;
        return sprites;
    }
    
    /*
     * Reine Debug-Methode: Zeichnet ein Netz über die Welt!
     */
    @Override
    public void drawGrid(){
        glDisable(GL_TEXTURE_2D);
        //Y-Linien
        for(int x = 0; x <= (panel.DISPLAY_WIDTH / Tile.TILE_WIDTH) && x < max_X_Grid;x++){
            
            glBegin(GL_LINES);
                glColor3f(255f,255f,0f);
                glVertex2f(x*Tile.TILE_WIDTH+dx_draw,0);
                glVertex2f(x*Tile.TILE_WIDTH+dx_draw,panel.DISPLAY_HEIGHT);
            glEnd();
        }
        //X-Linien       
        for(int y = 0; y <= (panel.DISPLAY_HEIGHT / Tile.TILE_WIDTH)+1 && y < max_X_Grid;y++){
            glBegin(GL_LINES);
                glColor3f(255f,255f,0f);
                glVertex2f(0,y*Tile.TILE_HEIGHT+dy_draw);
                glVertex2f(panel.DISPLAY_WIDTH,y*Tile.TILE_HEIGHT+dy_draw);
            glEnd();
        }
    }

    @Override
    public Tupel<Integer, Integer> getGridForPoint(Point2D.Float p) {
        float x = p.x;
        float y = p.y;
        return new Tupel(x/Tile.TILE_WIDTH,y/Tile.TILE_HEIGHT);
    }

    /*
     * Es wird berechnet welcher Abschnitt der Welt zu sehen ist!
     * 
     * Scrollen ist zur Zeit nur gridweise möglich.
     */
    
    @Override
    public void drawWorld() {
        if (initialized) {
            float draw_min_x = actualPoint.x - (panel.DISPLAY_WIDTH / 2) + dx_draw;
            float draw_min_y = actualPoint.y - (panel.DISPLAY_HEIGHT / 2) + dy_draw;
            float x_grid = draw_min_x / Tile.TILE_WIDTH;
            float y_grid = draw_min_y / Tile.TILE_HEIGHT;
            for (int x = 0; x <= (panel.DISPLAY_WIDTH / Tile.TILE_WIDTH) + 1; x++) {
                for (int y = 0; y <= (panel.DISPLAY_HEIGHT / Tile.TILE_HEIGHT) + 1; y++) {
                    if ((x + x_grid >= 0 && y + y_grid >= 0) && (x + x_grid < max_X_Grid && y + y_grid < max_Y_Grid)) {
                        tileArray[(int)(x + x_grid)][(int)(y + y_grid)].drawTile(x*Tile.TILE_WIDTH+dx_draw, y*Tile.TILE_WIDTH+dy_draw);
                    } else {
                        Tile.drawBlack(x*Tile.TILE_WIDTH+dx_draw,y*Tile.TILE_HEIGHT+dy_draw);
                    }
                }
            }
        }
    }
    
    @Override
    public void drawSprites(long delta){
        if(initialized && !actors.isEmpty()){
            float draw_min_x = actualPoint.x - (panel.DISPLAY_WIDTH / 2);
            float draw_max_x = actualPoint.x + (panel.DISPLAY_WIDTH / 2);
            float draw_min_y = actualPoint.y - (panel.DISPLAY_HEIGHT / 2);
            float draw_max_y = actualPoint.y + (panel.DISPLAY_HEIGHT / 2);
            Point2D.Float p = null;
            for(Sprite s:actors){
                
                p = s.getPosition();
                if((p.x >= draw_min_x && p.x <= draw_max_x) && (p.y >= draw_min_y && p.y <= draw_max_y)){
                    s.doLogic(delta);
                    s.drawObject();
                }
            }
        }
    }

    @Override
    public Tile getTileForPoint(Point2D.Float p) {
        if(p.x < 0 || p.y < 0){
            return null;
        }
        return tileArray[(int)(p.x/Tile.TILE_WIDTH)][(int)(p.y/Tile.TILE_HEIGHT)];
    }

    @Override
    public void initializeSprites() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
